通过 V4L2 读取摄像头

1
2#include <stdio.h>
3#include <stdlib.h>
4#include <string.h>
5#include <errno.h>
6#include <stdint.h>
7#include <stdbool.h>
8
9#include <unistd.h>
10#include <fcntl.h>
11#include <sys/ioctl.h>
12#include <sys/mman.h>
13#include <linux/videodev2.h> // libv4l-dev
14
15
16#define VIDEO_DEV "/dev/video0"
17
18int main()
19{
20    // 打开设备
21    int videoFd = open(VIDEO_DEV, O_RDWR);
22    if(videoFd < 0)
23    {
24        fprintf(stderr, "open %s failed: %s\n", VIDEO_DEV, strerror(errno));
25        return EXIT_FAILURE;
26    }
27
28    // 读取设备属性
29    struct v4l2_capability videoCap;
30    if(ioctl(videoFd, VIDIOC_QUERYCAP, &videoCap) < 0)
31    {
32        close(videoFd);
33        fprintf(stderr, "ioctl VIDIOC_QUERYCAP failed: %s\n", strerror(errno));
34        return EXIT_FAILURE;
35    }
36    printf("%s\n", videoCap.card);
37
38    // 判断是否是摄像头
39    if(videoCap.capabilities & V4L2_CAP_VIDEO_CAPTURE != V4L2_CAP_VIDEO_CAPTURE)
40    {
41        fprintf(stderr, "%s doesn't support video recording\n", VIDEO_DEV);
42        close(videoFd);
43        return EXIT_FAILURE;
44    }
45
46    // 读取支持的格式
47    bool supportYUYV = false;
48    struct v4l2_fmtdesc fmtdesc;
49    fmtdesc.index=0;
50    fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
51    printf("Support format:\n");
52    while(ioctl(videoFd, VIDIOC_ENUM_FMT, &fmtdesc) != -1)
53    {
54        printf("\t%d.%s\n",fmtdesc.index+1,fmtdesc.description);
55        fmtdesc.index++;
56        if(fmtdesc.pixelformat == V4L2_PIX_FMT_YUYV)
57        {
58            supportYUYV = true;
59        }
60    }
61
62    if(supportYUYV == false)
63    {
64        fprintf(stderr, "YUYV 4:2:2 not supported\n");
65        close(videoFd);
66        return EXIT_FAILURE;
67    }
68
69    // 设置帧格式
70    struct v4l2_format videoFormat;
71    videoFormat.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
72    videoFormat.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
73    if(ioctl(videoFd, VIDIOC_S_FMT, &videoFormat) < 0)
74    {
75        fprintf(stderr, "ioctl VIDIOC_S_FMT failed: %s\n", strerror(errno));
76        close(videoFd);
77        return EXIT_FAILURE;
78    }
79
80    // 申请缓冲
81    struct v4l2_requestbuffers request;
82    request.count = 3; //三帧缓冲
83    request.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
84    request.memory = V4L2_MEMORY_MMAP;
85    if(ioctl(videoFd, VIDIOC_REQBUFS, &request) < 0)
86    {
87        fprintf(stderr, "ioctl VIDIOC_REQBUFS failed: %s\n", strerror(errno));
88        close(videoFd);
89        return EXIT_FAILURE;
90    }
91
92    // 获取缓冲
93    struct v4l2_buffer buffer;
94    memset(&buffer, 0, sizeof(buffer));
95    buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
96    buffer.memory = V4L2_MEMORY_MMAP;
97    buffer.index = 0;
98    if(ioctl(videoFd, VIDIOC_QUERYBUF, &buffer) < 0)
99    {
100        fprintf(stderr, "ioctl VIDIOC_QUERYBUF failed: %s\n", strerror(errno));
101        close(videoFd);
102        return EXIT_FAILURE;
103    }
104    void* bufPtr = mmap(NULL, buffer.length, PROT_READ|PROT_WRITE, MAP_SHARED, videoFd, buffer.m.offset);
105    if(bufPtr == NULL)
106    {
107        fprintf(stderr, "mmap failed: %s\n", strerror(errno));
108        close(videoFd);
109        return EXIT_FAILURE;
110    }
111
112    // 创建一个帧缓冲
113    if(ioctl(videoFd, VIDIOC_QBUF, &buffer) < 0)
114    {
115        fprintf(stderr, "ioctl VIDIOC_QBUF failed: %s\n", strerror(errno));
116        close(videoFd);
117        return EXIT_FAILURE;
118    }
119
120    // 开始采集
121    enum v4l2_buf_type bufType = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
122    if(ioctl(videoFd, VIDIOC_STREAMON, &bufType) < 0)
123    {
124        fprintf(stderr, "ioctl VIDIOC_STREAMON failed: %s\n", strerror(errno));
125        close(videoFd);
126        return EXIT_FAILURE;
127    }
128
129    // 等待采集完成
130    fd_set fds;
131    FD_ZERO(&fds); 
132    FD_SET(videoFd,  &fds); 
133    struct timeval   tv; 
134    tv.tv_sec = 2;
135    tv.tv_usec = 0;
136    select(1, &fds, NULL, NULL, &tv);
137
138    // 读取一帧图像并删除帧缓冲
139    if(ioctl(videoFd, VIDIOC_DQBUF, &buffer) < 0)
140    {
141        fprintf(stderr, "ioctl VIDIOC_DQBUF failed: %s\n", strerror(errno));
142        close(videoFd);
143        return EXIT_FAILURE;
144    }
145
146    // 停止采集
147    ioctl(videoFd, VIDIOC_STREAMOFF, &bufType);
148
149    // 创建一个PPM文件
150    FILE* ppmFptr = fopen("output.ppm", "wb");
151    if(ppmFptr == NULL)
152    {
153        fprintf(stderr, "%s\n", strerror(errno));
154        close(videoFd);
155        return EXIT_FAILURE;
156    }
157
158    // 写入PPM header
159    fprintf(ppmFptr, "P3\n%d %d\n255\n", videoFormat.fmt.pix.width, videoFormat.fmt.pix.height);
160
161    // 读取像素,写入PPM文件
162    for(size_t i = 0; i+3 < buffer.length; i+=4)
163    {
164        uint8_t Y1 = ((uint8_t*)bufPtr)[i];
165        uint8_t U = ((uint8_t*)bufPtr)[i+1];
166        uint8_t Y2 = ((uint8_t*)bufPtr)[i+2];
167        uint8_t V = ((uint8_t*)bufPtr)[i+3];
168
169        uint8_t B =  1.164 * (Y1 - 16) +  2.018 * (U - 128);
170        uint8_t G =  1.164 * (Y1 - 16) -  0.391 * (U - 128) - 0.813 * (V - 128);
171        uint8_t R =  1.164 * (Y1 - 16)                      + 1.596 * (V - 128);
172        fprintf(ppmFptr, "%u %u %u ", R, G, B);
173
174        B =  1.164 * (Y2 - 16) +  2.018 * (U - 128);
175        G =  1.164 * (Y2 - 16) -  0.391 * (U - 128) - 0.813 * (V - 128);
176        R =  1.164 * (Y2 - 16)                      + 1.596 * (V - 128);
177        fprintf(ppmFptr, "%u %u %u ", R, G, B);
178    }
179    
180    close(videoFd);
181    fclose(ppmFptr);
182    return EXIT_SUCCESS;
183}